# -*- coding: utf-8 -*-

# A dictionary of movie critics and their ratings of a small
# set of movies
# critics={'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
#  'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5, 
#  'The Night Listener': 3.0},
# 'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5, 
#  'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0, 
#  'You, Me and Dupree': 3.5}, 
# 'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
#  'Superman Returns': 3.5, 'The Night Listener': 4.0},
# 'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
#  'The Night Listener': 4.5, 'Superman Returns': 4.0, 
#  'You, Me and Dupree': 2.5},
# 'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 
#  'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
#  'You, Me and Dupree': 2.0}, 
# 'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
#  'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
# 'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}}


critics={'Lisa Rose': 
            {'Lady in the Water':   2.5, 
             'Snakes on a Plane':   3.5,
             'Just My Luck':        3.0, 
             'Superman Returns':    3.5, 
             'You, Me and Dupree':  2.5, 
             'The Night Listener':  3.0},
         'Gene Seymour': 
            {'Lady in the Water':   3.0, 
             'Snakes on a Plane':   3.5, 
             'Just My Luck':        1.5, 
             'Superman Returns':    5.0, 
             'The Night Listener':  3.0,  
             'You, Me and Dupree':  3.5}, 
         'Michael Phillips': 
            {'Lady in the Water':   2.5, 
             'Snakes on a Plane':   3.0, 
             'Superman Returns':    3.5, 
             'The Night Listener':  4.0},
         'Claudia Puig': 
            {'Snakes on a Plane':   3.5, 
             'Just My Luck':        3.0, 
             'The Night Listener':  4.5, 
             'Superman Returns':    4.0,  
             'You, Me and Dupree':  2.5},
         'Mick LaSalle': 
            {'Lady in the Water':   3.0, 
             'Snakes on a Plane':   4.0,  
             'Just My Luck':        2.0, 
             'Superman Returns':    3.0, 
             'The Night Listener':  3.0, 
             'You, Me and Dupree':  2.0}, 
         'Jack Matthews': 
            {'Lady in the Water':   3.0, 
             'Snakes on a Plane':   4.0, 
             'The Night Listener':  3.0, 
             'Superman Returns':    5.0, 
             'You, Me and Dupree':  3.5},
         'Toby': 
            {'Snakes on a Plane':   4.5,
             'You, Me and Dupree':  1.0,
             'Superman Returns':    4.0}
         }

from math import sqrt
 
# 相关度评价
def sim_distance(prefs,person1,person2):
    si = {}
    for item in prefs[person1]:
        if item in prefs[person2]:
            si[item] = 1
    
    if len(si)==0:
        return 0
       
    sum_of_squares = sum(
            [ pow(prefs[person1][item]-prefs[person2][item],2) 
                        for item in prefs[person1] 
                        if item in prefs[person2]
            ])
      
    return 1/(1+sqrt(sum_of_squares))

# 皮尔逊相关系数
def pearson(x,y):
    n = len(x)
    vals = range(len(x))
     
    sumx = sum([float(x[i]) for i in vals])
    sumy = sum([float(y[i]) for i in vals])
#     print sumx
#     print sumy
     
    sumxsq = sum([x[i]**2.0 for i in vals])
    sumysq = sum([y[i]**2.0 for i in vals])
#     print sumxsq
#     print sumysq
     
    psum = sum([x[i]*y[i] for i in vals])
#     print psum
     
    num = psum-((sumx*sumy)/n)
    den = sqrt((sumxsq-(sumx**2.0)/n)*(sumysq-(sumy**2.0)/n))
     
    if den==0:return 1
     
    return num/den
 
def sim_pearson(prefs, p1, p2):
    x = []
    y = []
    for it in prefs[p1]:
        #x.append(prefs[p1][it])
        if it in prefs[p2]:
            y.append(prefs[p2][it])
            x.append(prefs[p1][it])
#         else:
#             y.append(0)
             
    if len(y)==0:
        return 1
     
    return pearson(x, y)
 
# # Returns the Pearson correlation coefficient for p1 and p2
# def sim_pearson(prefs,p1,p2):
#   # Get the list of mutually rated items
#   si={}
#   for item in prefs[p1]: 
#     if item in prefs[p2]: si[item]=1
#  
#   # if they are no ratings in common, return 0
#   if len(si)==0: return 0
#  
#   # Sum calculations
#   n=len(si)
#    
#   # Sums of all the preferences
#   sum1=sum([prefs[p1][it] for it in si])
#   sum2=sum([prefs[p2][it] for it in si])
#    
#   # Sums of the squares
#   sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
#   sum2Sq=sum([pow(prefs[p2][it],2) for it in si])	
#    
#   # Sum of the products
#   pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
#    
#   # Calculate r (Pearson score)
#   num=pSum-(sum1*sum2/n)
#   den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
#   if den==0: return 0
#  
#   r=num/den
#  
#   return r
# 

 
# Returns the best matches for person from the prefs dictionary. 
# Number of results and similarity function are optional params.
def topMatches(prefs,person,n=5,similarity=sim_pearson):
    scores=[(similarity(prefs,person,other),other) 
                  for other in prefs if other!=person]
    scores.sort()
    scores.reverse()
    return scores[0:n]
 
# Gets recommendations for a person by using a weighted average
# of every other user's rankings
def getRecommendations(prefs,person,similarity=sim_pearson):
    totals={}
    simSums={}
    for other in prefs:
    # don't compare me to myself
        if other==person: continue
        sim=similarity(prefs,person,other)
 
    # ignore scores of zero or lower
        if sim<=0: continue
        for item in prefs[other]:
            # only score movies I haven't seen yet
            if item not in prefs[person] or prefs[person][item]==0:
                # Similarity * Score
                totals.setdefault(item,0)
                totals[item]+=prefs[other][item]*sim
                # Sum of similarities
                simSums.setdefault(item,0)
                simSums[item]+=sim
 
    # Create the normalized list
    rankings=[(total/simSums[item],item) for item,total in totals.items()]
 
    # Return the sorted list
    rankings.sort()
    rankings.reverse()
    return rankings
 
def transformPrefs(prefs):
    result={}
    for person in prefs:
        for item in prefs[person]:
            result.setdefault(item,{})
            # Flip item and person
            result[item][person]=prefs[person][item]
    return result
 
 
def calculateSimilarItems(prefs,n=10):
    # Create a dictionary of items showing which other items they
    # are most similar to.
    result={}
    # Invert the preference matrix to be item-centric
    itemPrefs=transformPrefs(prefs)
    c=0
    for item in itemPrefs:
        # Status updates for large datasets
        c+=1
        if c%100==0: print "%d / %d" % (c,len(itemPrefs))
        # Find the most similar items to this one
        scores=topMatches(itemPrefs,item,n=n,similarity=sim_distance)
        result[item]=scores
    return result
 
def getRecommendedItems(prefs,itemMatch,user):
    userRatings=prefs[user]
    scores={}
    totalSim={}
    # Loop over items rated by this user
    for (item,rating) in userRatings.items( ):
 
        # Loop over items similar to this one
        for (similarity,item2) in itemMatch[item]:
 
            # Ignore if this user has already rated this item
            if item2 in userRatings: continue
            # Weighted sum of rating times similarity
            scores.setdefault(item2,0)
            scores[item2]+=similarity*rating
            # Sum of all the similarities
            totalSim.setdefault(item2,0)
            totalSim[item2]+=similarity
 
    # Divide each total score by total weighting to get an average
    rankings=[(score/totalSim[item],item) for item,score in scores.items( )]
 
    # Return the rankings from highest to lowest
    rankings.sort( )
    rankings.reverse( )
    return rankings
 
# def loadMovieLens(path='/data/movielens'):
#   # Get movie titles
#   movies={}
#   for line in open(path+'/u.item'):
#     (id,title)=line.split('|')[0:2]
#     movies[id]=title
#   
#   # Load data
#   prefs={}
#   for line in open(path+'/u.data'):
#     (user,movieid,rating,ts)=line.split('\t')
#     prefs.setdefault(user,{})
#     prefs[user][movies[movieid]]=float(rating)
#   return prefs


if __name__ == '__main__':
    #print critics['Gene Seymour']['Just My Luck']
    #print sim_distance(critics, 'Lisa Rose', 'Gene Seymour')
    #print sim_pearson(critics, 'Mick LaSalle', 'Gene Seymour')
    #print sim_pearson(critics, 'Gene Seymour', 'Mick LaSalle')
#     print topMatches(critics, "Toby",6)
    #print getRecommendations(critics, 'Toby')
    #print getRecommendations(critics, 'Toby', sim_distance)
    
#     movies = transformPrefs(critics)
#     print topMatches(movies, 'Superman Returns')
#     print getRecommendations(movies, 'Just My Luck')

#     print calculateSimilarItems(critics)
#     sim = calculateSimilarItems(critics)
#     for it in sim:
#         print "{'",it,"':"
#         for t in sim[it]:
#             print "\t",t,","
#         print "}"

    itemsim = calculateSimilarItems(critics)
    print getRecommendedItems(critics, itemsim, 'Toby')
    
    



